TP n°1 : Cyanobactéries et Chlorophylle

HAX004X - Dirty Data

Auteur·rice·s

AIGOIN Emilie

LABOURAIL Célia

Date de publication

12 janvier 2026

1 Introduction


Dans ce rapport, nous détaillons notre travail qui avait pour objectif de lisser des données ainsi que de les agréger en blocs de temps d’une durée d’une heure et quart.

Notre jeu de données provient de capteurs dans un lac en région parisienne qui mesure la concentration en chlorophylle en microgrammes par litre (µg/L) ainsi que la concentration en cyanobactéries (µg/L). Ces dernières sont des micro-organismes photosynthétiques qui peuvent proliférer dans les eaux douces et former des blooms nuisibles pour l’environnement et la santé humaine. Il est important de préciser que les cyanobactéries sont des bactéries photosynthétiques qui possèdent de la chlorophylle.

L’échantillonnage initial pour ces mesures est de 15 minutes, mais à cette granularité l’évolution des concentrations est quasiment imperceptible. De plus, cela pourrait fausser les analyses statistiques en introduisant une forte autocorrélation entre les observations successives. En regroupant les données en blocs de 1h15, nous nous attendons à obtenir une vision plus claire des tendances et des variations des concentrations de chlorophylle et de cyanobactéries dans le lac.

Le long de ce travail, nous utiliserons le langage de programmation Julia, qui permet les applications de calcul numérique et la visualisation graphique. Tout nos codes sont disponibles en opensource sur notre page Github via ce lien.

2 Visualisation des données


Tout d’abord, afin de comprendre un peu mieux notre jeu de données, nous avons commencé par effectuer des visualisations. Pour cela, nous commençons par charger les bibliothèques nécessaires à la manipulation des données, à la gestion des dates et à la visualisation.

Code
# Importer les packages

using CSV
using DataFrames
using Dates
using Statistics
using Plots
using RollingFunctions

Les données sont ensuite importées dans un tableau (dataframe), ce qui va nous permettre une manipulation structurée de nos données.

Code
# Importer les données

data = CSV.read("data_chl_cyano.csv", DataFrame)
first(data, 5)
5×3 DataFrame
Row date Chl Cyano
String31 Float64? Float64?
1 15/10/2022 00:01 5.13 3.87
2 15/10/2022 00:16 4.78 3.51
3 15/10/2022 00:31 5.0 3.09
4 15/10/2022 00:46 4.55 3.85
5 15/10/2022 01:01 5.01 3.18

Suite à l’importation de nos données sous forme d’un dataframe Julia et la visualisation des cinq premières lignes, nous observons trois variables principales :

  • La date de mesure avec l’horaire associée (date).
  • La concentration en chlorophylle (Chl).
  • La concentratione n cyanobactéries (Cyano).

De plus, nous remarquons que la colonne de date est de type String31, e qui empêche toute manipulation temporelle. Ainsi, nous convertissons cette colonne en format DateTime afin de permettre des opérations temporelles telles que l’agrégation ou la représentation graphique en fonction du temps.

Code
# Préparation des dates et des horaires

data.date = tryparse.(DateTime, data.date, dateformat"dd/mm/yyyy HH:MM")

println("Premières lignes :")
display(first(data, 5))

println("\nDernières lignes :")
display(last(data, 5))
Premières lignes :
5×3 DataFrame
Row date Chl Cyano
DateTime Float64? Float64?
1 2022-10-15T00:01:00 5.13 3.87
2 2022-10-15T00:16:00 4.78 3.51
3 2022-10-15T00:31:00 5.0 3.09
4 2022-10-15T00:46:00 4.55 3.85
5 2022-10-15T01:01:00 5.01 3.18

Dernières lignes :
5×3 DataFrame
Row date Chl Cyano
DateTime Float64? Float64?
1 2024-08-31T16:16:00 65.75 2.15
2 2024-08-31T16:31:00 41.02 2.36
3 2024-08-31T16:46:00 44.76 2.29
4 2024-08-31T17:01:00 39.5 2.01
5 2024-08-31T17:16:00 42.19 2.67

Nous avons commencé par utiliser la fonction DateTime mais celle-ci ne fonctionnait pas car certaines lignes étaient mals formattées. Nous avons donc essayé une méthode plus robuste (fonction tryparse) qui transforme les dates incalides en missing plutôt que faire planter le code.

Nous cherchons maintenant à observer l’évolution temporelle de la concentration en chlorophylle à la résolution initiale de 15 minutes (sans transformation ni lissage). Puis celle de la concentration en cyanobactéries en fonction du temps.

Code
# Visualisation graphique de la chlorophylle

plot(
data.date,
data.Chl,
xlabel = "Temps (intervalles de 15 minutes)",
ylabel = "Chlorophylles (µg/L)",
title = "Évolution brute de la concentration en chlorophylles",
legend = false,
color = "#6495ED"
)
Code
# Visualisation graphique de la cyanobactérie

plot(
data.date,
data.Cyano,
xlabel = "Temps (intervalles de 15 minutes)",
ylabel = "Cyanobactéries (µg/L)",
title = "Évolution brute de la concentration en cyanobactéries",
legend = false,
color = "#6495ED"
)

Nos figures représentent l’évolution brute de la concentration en chlorophylle et en cyanobactéries en fonction du temps (du 15 octobre 2022 au 31 août 2024). Elles mettent en évidence une forte variabilité temporelle, c’est-à-dire que, pour les deux variables, les valeurs sont généralement faubles mais ponctuées de pics importants, parfois très marqués, apparaissant de manière irrégulières au cours du temps.

Ces pics pourraient refléter des évènements biologiques réels mais pourraient également être dûes à des bruits de mesures des capteurs, à des variations lcoales du courant dans le lac ou encore à des artéfacts de mesure. Cette granularité de mesurfe trop fine (15 minutes) empêche de distinguer clairement les tendances biologiques réelles des variations aléatoires.

Par ailleurs, nous constatons que les profils temporels de la chlorophylle et des cyanobactéries présentent des similitudes, ce qui est cohérent du point de vue biologique puisque les cyanobactéries contiennent de la chlorophylle. Néanmoins, les pics de cyanobactérieues sont généralement moins élevés et moins fréquents que ceux de la chlorophylle totale.

Face à ces observations, il apparaît important de procéder à un lissage et une agrégation de données pour plusieurs raisons.

Tout d’abord, pour réduire le bruit de mesure. En effet, les capteurs peuvent générer des fluctuations artificielles dûes à des soucis techniques. Le lissage pourrait permettre d’atténuer ces variations parasites qui ne reflètent pas la dynamique réelle des concentrations.

Ensuite, pour mettre en évidence les tendances biologiques. En effet, les processus biologiques tels que la croissance des cyanobactéries ou la phyotosynthèse se déroulent sur des échelles de temps plus longues que 15 minutes. En regroupant les données par blocs de 1h15, nous nous plaçons à une échelle temporelle plus cohérente avec ces phénomènes biologiques, ce qui facilitera l’identification des évolution écologiques.

De plus, pour réduire l’autocorrélation. En effet, à l’échelle de 15 minutes, les mesures successives sont très fortement corrélées entre elles, ce qui est contre les hypothèses de nombreux tests statistiques, car cela peut conduite à surestimer la significativité des résultats. L’agrégation par blocs de 1h15 permet de réduire cette dépendance temporelle et d’obtenir des observations plus indépendantes.

Pour finir, pour facilier l’analyse. En effet, un jeu de données moins volumineux et moins bruité est plus facile à manipuler, à visualiser et à analyser. Cela permet également de réduire les coûts de calcul pour les analyses ultérieures.

Le choix spécifique d’une fenêtre de 1h15 semble représenter un bon compromis entre la conservation d’une résolution temporelle suffisante pour observer les dynamiques journalières et l’obtention d’une réduction signification du bruit et du volume de données.

3 Regroupement des données


Nous allons maintenant procéder à l’agrégation des données par blocs de 1h15 (75 minutes). Pour cela nous devons :

  • Créer une nouvelle colonne qui attribue à chaque mesure un bloc temporel.
  • Regrouper les mesures appartenant au même bloc.
  • Calculer une statistique agrégée (moyenne ou médiane) pour chaque bloc.

La première étape consiste donc à procéder à l’agrégation des données. Comme nos mesures sont effectuées toutes les 15 minutes, chaque bloc contiendra exactement 5 mesures consécutives (5 x 15 min = 75 min).

Code
# Créer les numéros de blocs : 1,1,1,1,1, 2,2,2,2,2, 3,3,3,3,3, etc.
n_lignes = nrow(data)
data.bloc = repeat(1:ceil(Int, n_lignes/5), inner=5)[1:n_lignes]

# Afficher les premières lignes pour vérifier
first(data, 15)
15×4 DataFrame
Row date Chl Cyano bloc
DateTime Float64? Float64? Int64
1 2022-10-15T00:01:00 5.13 3.87 1
2 2022-10-15T00:16:00 4.78 3.51 1
3 2022-10-15T00:31:00 5.0 3.09 1
4 2022-10-15T00:46:00 4.55 3.85 1
5 2022-10-15T01:01:00 5.01 3.18 1
6 2022-10-15T01:16:00 4.86 3.29 2
7 2022-10-15T01:31:00 5.03 3.33 2
8 2022-10-15T01:46:00 4.84 3.24 2
9 2022-10-15T02:01:00 4.77 2.94 2
10 2022-10-15T02:16:00 5.2 3.36 2
11 2022-10-15T02:31:00 4.62 3.07 3
12 2022-10-15T02:46:00 4.52 3.11 3
13 2022-10-15T03:01:00 4.36 3.15 3
14 2022-10-15T03:16:00 4.41 3.03 3
15 2022-10-15T03:31:00 5.45 3.07 3

Les dates confirment bien qu’il y a bien 15 minutes d’écart entre chaque mesure. Nous allons donc calculer la moyenen de la chlorophylle et des cyanobactéries pour chaque bloc de 1h15.

Code
# Calculer les moyennes par bloc
data_agregee = combine(groupby(data, :bloc),
    :date => first => :date_debut,      # Date de début du bloc
    :Chl => mean => :Chl_moyenne,       # Moyenne de la chlorophylle
    :Cyano => mean => :Cyano_moyenne    # Moyenne des cyanobactéries
)

# Afficher les premières et dernières lignes
println("Premières lignes agrégées :")
display(first(data_agregee, 10))

println("\nDernières lignes agrégées :")
display(last(data_agregee, 10))
Premières lignes agrégées :
10×4 DataFrame
Row bloc date_debut Chl_moyenne Cyano_moyenne
Int64 DateTime Float64? Float64?
1 1 2022-10-15T00:01:00 4.894 3.5
2 2 2022-10-15T01:16:00 4.94 3.232
3 3 2022-10-15T02:31:00 4.672 3.086
4 4 2022-10-15T03:46:00 4.428 2.986
5 5 2022-10-15T05:01:00 4.022 2.946
6 6 2022-10-15T06:16:00 4.126 2.824
7 7 2022-10-15T07:31:00 3.768 2.798
8 8 2022-10-15T08:46:00 4.028 2.852
9 9 2022-10-15T10:01:00 3.682 2.896
10 10 2022-10-15T11:16:00 3.738 2.968

Dernières lignes agrégées :
10×4 DataFrame
Row bloc date_debut Chl_moyenne Cyano_moyenne
Int64 DateTime Float64? Float64?
1 12293 2024-08-31T05:46:00 28.596 2.136
2 12294 2024-08-31T07:01:00 30.492 2.25
3 12295 2024-08-31T08:16:00 31.83 2.088
4 12296 2024-08-31T09:31:00 33.53 2.134
5 12297 2024-08-31T10:46:00 37.58 2.246
6 12298 2024-08-31T12:01:00 34.49 2.142
7 12299 2024-08-31T13:16:00 41.77 2.094
8 12300 2024-08-31T14:31:00 38.862 2.02
9 12301 2024-08-31T15:46:00 47.366 2.264
10 12302 2024-08-31T17:01:00 40.845 2.34

Nous remarquons, au-délà du fait que les agrégations ont bien fonctionnées, que la chlorophylle semble avoir fortement augmenté en fin de période (été 2024). En revanche, les syanobactéries restent relativements stables autour de 1-3 µg/L, même s’il semble y avoir une légère diminution.

Afin de vérifier si la lissage a bien fonctionné et si les tendances sont plus claires, nous allons visualiser nos nouvelles données.

Code
# Graphique de la chlorophylle agrégée
plot(
    data_agregee.date_debut,
    data_agregee.Chl_moyenne,
    xlabel = "Temps (intervalles de 1h15)",
    ylabel = "Chlorophylles moyennes (µg/L)",
    title = "Évolution de la concentration en chlorophylles",
    legend = false,
    linewidth = 2, 
    color = "#6495ED"
)
Code
# Graphique des cyanobactéries agrégées
plot(
    data_agregee.date_debut,
    data_agregee.Cyano_moyenne,
    xlabel = "Temps (intervalles de (1h15)",
    ylabel = "Cyanobactéries moyennes (µg/L)",
    title = "Évolution de la concentration en cyanobactéries",
    legend = false,
    linewidth = 2, 
    color = "#6495ED"
    )

Il nous semble que les courbes ont bien été lissées. En effet, nous voyons que les concentrations maximales ont extrêmement dimunuées dans les deux cas (de 600 à 200 µg/L pour les chlorophylles et de 85 à 20 µg/L pour les cyanobactéries).

Pour mieux observer l’effet du lissage, nous superposons sur un même graphique les données brutes (mesures toutes les 15 minutes) et les données agrégées (blocs de 1h15).

Code
# Comparaison pour la chlorophylle
plot(
    data.date,
    data.Chl,
    label = "Données brutes (15 min)",
    xlabel = "Temps",
    ylabel = "Chlorophylles (µg/L)",
    title = "Données brutes vs agrégées (Chlorophylles)",
    alpha = 0.4,  # Transparence pour les données brutes
    color = "#2563eb"
)

plot!(
    data_agregee.date_debut,
    data_agregee.Chl_moyenne,
    label = "Données agrégées (1h15)",
    linewidth = 2,
    color = :darkred
)
Code
# Comparaison pour les cyanobactéries
plot(
    data.date,
    data.Cyano,
    label = "Données brutes (15 min)",
    xlabel = "Temps",
    ylabel = "Cyanobactéries (µg/L)",
    title = "Connées brutes vs agrégées (Cyanobactéries)",
    alpha = 0.4,  # Transparence pour les données brutes
    color = "#2563eb"
)

plot!(
    data_agregee.date_debut,
    data_agregee.Cyano_moyenne,
    label = "Données agrégées (1h15)",
    linewidth = 2,
    color = :darkred
)

Ces graphiques mettent clairement en évidence l’effet du lissage par agrégation.

Pour la chlorophylle, les données brutes (en bleu) présentent une très forte variabilité avec des pics extrêmement marqués et de nombreuses fluctuations rapides. La courbe agrégée (en rouge foncé) suit la même tendance générale mais de manière beaucoup plus lisse et régulière. On observe que les pics importants sont conservés (début 2023, début 2024, etc.), mais leur amplitude est légèrement atténuée. Le lissage permet de distinguer plus clairement les périodes de forte concentration (blooms) des périodes calmes, sans être perturbé par le bruit de mesure.

Pour les cyanobactéries, le même phénomène s’observe avec la courbe bleue (données brutes) présentant des pics très abrupts, notamment un pic majeur au printemps 2023 atteignant près de 90 µg/L, et plusieurs autres pics en 2024. La courbe agrégée (en rouge foncé) atténue ces variations tout en conservant l’information sur les événements de prolifération. On remarque que les cyanobactéries sont globalement à des niveaux faibles (< 5 µg/L) la majorité du temps, avec des blooms ponctuels et saisonniers.

4 Lissage supplémentaire par moyenne mobile


Bien que l’agrégation par blocs de 1h15 ait permis de réduire significativement le bruit de mesure et d’am&liorer la lisibilité des données, certaines fluctuations à court terme persistent encore dans les séries temporelles. Ces variations peuvent masquer les tendances que nous cherchons à identifier.

La moyenne mobile est une technique de lissage classique qui consiste à calculer la moyenne d’une fenêtre glissante de \(n\) observations consécutives. Concrètement, chaque point lissé correspond à la moyenne de lui-même et de ses voisins immédiats. Cette approche semble présenter plusieurs avantages pour notre analyse notamment la réduction du bruit (en moyennant plusieurs observations consécutives, les fulctuations s’atténuent tandis que les tendances réelles se renforcent) mais également l’interprétation (les résultats restent dans les mêmes unités que les données originales et le nombre de mesures n’est pas affecté).

Cependant, le point le plus important est de choicir une taille de fenêtre adaptée : une fenêtre trop petite ne lissera pas suffisamment les données tandis qu’une fenêtre trop grande risque d’effacer des évènements importants. Il est possible de déterminer la fenêtre optimale par cross-validation, cependant, nous avons choisi de tester visuellemtn plusieurs tailles de fenêtre (5, 10, 25, 50, 100 et 500 observations) afin de comparer leurs effets sur le lissage et de sélectionner celle qui offre le meilleur compromis entre réduction du bruit et préservation des évènements importants.

Nous appliquons maintenant la moyenne mobile à nos données agrégées en utilisant le package RollingFunctions qui fournit des outils pour les calculs sur fenêtres glissantes.

Code
# Tester différentes tailles de fenêtres
fenetres = [5, 10, 25, 50, 100, 500]

# Créer les colonnes pour chaque fenêtre
for f in fenetres
    data_agregee[!, Symbol("Chl_lissee_$f")] = runmean(data_agregee.Chl_moyenne, f)
    data_agregee[!, Symbol("Cyano_lissee_$f")] = runmean(data_agregee.Cyano_moyenne, f)
end

# Comparaison graphique en 2x2 pour la chlorophylle
p1 = plot(
    data_agregee.date_debut, data_agregee.Chl_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Chlorophylle (µg/L)", 
    title = "Fenêtre = 5"
)
plot!(p1, data_agregee.date_debut, data_agregee.Chl_lissee_5,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p2 = plot(
    data_agregee.date_debut, data_agregee.Chl_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Chlorophylle (µg/L)", 
    title = "Fenêtre = 10"
)
plot!(p2, data_agregee.date_debut, data_agregee.Chl_lissee_10,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p3 = plot(
    data_agregee.date_debut, data_agregee.Chl_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Chlorophylle (µg/L)", 
    title = "Fenêtre = 25"
)
plot!(p3, data_agregee.date_debut, data_agregee.Chl_lissee_25,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p4 = plot(
    data_agregee.date_debut, data_agregee.Chl_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Chlorophylle (µg/L)", 
    title = "Fenêtre = 50"
)
plot!(p4, data_agregee.date_debut, data_agregee.Chl_lissee_50,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p5 = plot(
    data_agregee.date_debut, data_agregee.Chl_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Chlorophylle (µg/L)", 
    title = "Fenêtre = 100"
)
plot!(p5, data_agregee.date_debut, data_agregee.Chl_lissee_100,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p6 = plot(
    data_agregee.date_debut, data_agregee.Chl_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Chlorophylle (µg/L)", 
    title = "Fenêtre = 500"
)
plot!(p6, data_agregee.date_debut, data_agregee.Chl_lissee_500,
    label = "Lissée", linewidth = 2, color = :darkgreen)

plot(p1, p2, p3, p4, p5, p6, layout = (6, 1), size = (680, 2000),
    plot_title = "Comparaison des fenêtres de moyenne mobile - Chlorophylle",
    plot_titlefontsize = 12)

La comparaison des différentes tailles de fenêtre pour la moyenne mobile révèle des différences importantes dans le lissage des données de chlorophylle.

Pour les fenêtres de 5 et 10 observations, le lissage reste insuffisant. En effet, bien que la courbe lissée (en vert foncé) suive globalement la tendance des données agrégées (en rouge), elle conserve encore de nombreuses fluctuations à court terme. Ces variations rapides empêchent une lecture claire des tendances biologiques à moyen et long terme. Ces fenêtres semblent trop petites pour atténuer efficacement le bruit de mesure résiduel présent dans les données agrégées.

À l’inverse, la fenêtre de 500 observations produit un lissage excessif. La courbe associée est tellement atténuée qu’elle gomme certains événements biologiques importants. On observe notamment une perte d’information sur certains pics de concentration, et la courbe présente même des discontinuités ou des “trous” au milieu de la série temporelle, probablement dus à la présence de valeurs manquantes qui affectent fortement les moyennes sur une fenêtre aussi large. Ce niveau de lissage semble donc inadapté car il altère la réalité biologique des blooms de chlorophylle.

Les fenêtres de 50 et 100 observations (correspondant respectivement à environ 62h30 et 125 heures, soit environ 2,6 et 5,2 jours) apparaissent comme les plus appropriées. Elles permettent de réduire les fluctuations parasites tout en préservant les événements majeurs. La courbe lissée avec ces fenêtres semble suivre les tendances saisonnières et met en évidence les périodes de forte concentration en chlorophylle (notamment les pics importants observés au début 2023 et 2024) sans les déformer excessivement. Ce compromis semble cohérent avec les échelles temporelles des processus biologiques que nous cherchons à étudier qui se déroulent généralement sur quelques jours.

Maintenant, afin de vérifier que la fenêtre de 50 observations ne lisse pas de manière excessive les données et ne masque pas des variations biologiques pertinentes à court terme, nous proposons d’examiner plus en détail une période restreinte de 6 mois. Cette analyse nous permettra d’observer plus finement l’effet du lissage sur les dynamiques à une échelle temporelle plus courte et de nous assurer que les événements biologiques rapides restent identifiables. Nous sélectionnons la période du 1er janvier 2024 au 30 juin 2024, qui présente plusieurs événements de prolifération intéressants.

Code
# Définir la période d'intérêt
date_debut_zoom = DateTime(2024, 1, 1)
date_fin_zoom = DateTime(2024, 6, 30)

# Filtrer les données brutes
data_zoom = filter(row -> !ismissing(row.date) && 
                         date_debut_zoom <= row.date <= date_fin_zoom, 
                   data)

# Filtrer les données agrégées
data_agregee_zoom = filter(row -> !ismissing(row.date_debut) && 
                                  date_debut_zoom <= row.date_debut <= date_fin_zoom, 
                          data_agregee)

# Graphique zoom pour la chlorophylle
plot(
    data_agregee_zoom.date_debut,
    data_agregee_zoom.Chl_moyenne,
    label = "Données agrégées (1h15)",
    alpha = 0.6,
    linewidth = 1.5,
    color = :darkred
)

plot!(
    data_agregee_zoom.date_debut,
    data_agregee_zoom.Chl_lissee_50,
    label = "Données lissées (fenêtre = 50)",
    linewidth = 2.5,
    color = :darkgreen
)

Ce zoom sur la période janvier-juin 2024 confirme que la fenêtre de 50 observations préserve bien les dynamiques biologiques à court et moyen terme. Nous observons distinctement deux événements majeurs de prolifération : un premier bloom en mars 2024 atteignant environ 150 µg/L, et un second plus important en avril-mai avec plusieurs pics successifs.

La courbe lissée (en vert) suit fidèlement la trajectoire des données agrégées (en rouge). Nous remarquons que les phases de croissance et de déclin des blooms sont parfaitement conservées, avec leurs dynamiques temporelles respectives. Le pic de mars montre une montée relativement progressive suivie d’une descente plus rapide, tandis que l’événement d’avril-mai présente une structure plus complexe avec plusieurs pics rapprochés, ce qui suggère des conditions environnementales fluctuantes favorisant des blooms successifs.

Même à cette échelle temporelle réduite, le lissage ne masque aucun événement biologique significatif et permet une lecture claire des tendances tout en éliminant le bruit de mesure. Cette analyse valide donc notre choix d’une fenêtre de 50 observations comme compromis optimal pour l’étude des dynamiques du phytoplancton dans ce lac.

Nous allons maintenant effectuer ce lissage par moyenne mobile pour les données en cyanobactéries.

Code
# Comparaison graphique pour les cyanobactéries
p1_cyano = plot(
    data_agregee.date_debut, data_agregee.Cyano_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Cyanobactéries (µg/L)", 
    title = "Fenêtre = 5"
)
plot!(p1_cyano, data_agregee.date_debut, data_agregee.Cyano_lissee_5,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p2_cyano = plot(
    data_agregee.date_debut, data_agregee.Cyano_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Cyanobactéries (µg/L)", 
    title = "Fenêtre = 10"
)
plot!(p2_cyano, data_agregee.date_debut, data_agregee.Cyano_lissee_10,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p3_cyano = plot(
    data_agregee.date_debut, data_agregee.Cyano_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Cyanobactéries (µg/L)", 
    title = "Fenêtre = 25"
)
plot!(p3_cyano, data_agregee.date_debut, data_agregee.Cyano_lissee_25,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p4_cyano = plot(
    data_agregee.date_debut, data_agregee.Cyano_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Cyanobactéries (µg/L)", 
    title = "Fenêtre = 50"
)
plot!(p4_cyano, data_agregee.date_debut, data_agregee.Cyano_lissee_50,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p5_cyano = plot(
    data_agregee.date_debut, data_agregee.Cyano_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Cyanobactéries (µg/L)", 
    title = "Fenêtre = 100"
)
plot!(p5_cyano, data_agregee.date_debut, data_agregee.Cyano_lissee_100,
    label = "Lissée", linewidth = 2, color = :darkgreen)

p6_cyano = plot(
    data_agregee.date_debut, data_agregee.Cyano_moyenne,
    label = "Agrégée", alpha = 0.3, color = :darkred,
    xlabel = "Temps", ylabel = "Cyanobactéries (µg/L)", 
    title = "Fenêtre = 500"
)
plot!(p6_cyano, data_agregee.date_debut, data_agregee.Cyano_lissee_500,
    label = "Lissée", linewidth = 2, color = :darkgreen)

plot(p1_cyano, p2_cyano, p3_cyano, p4_cyano, p5_cyano, p6_cyano, 
    layout = (6, 1), size = (680, 2000),
    plot_title = "Comparaison des fenêtres de moyenne mobile - Cyanobactéries",
    plot_titlefontsize = 12)

Comme précédemment observé pour la chlorophylle, la comparaison des différentes tailles de fenêtre pour les cyanobactéries révèle des tendances similaires en termes d’efficacité du lissage. Les fenêtres de 5 et 10 observations demeurent insuffisantes pour atténuer le bruit de mesure. À l’opposé, la fenêtre de 500 observations produit un lissage trop important, tandis que les fenêtres de 50 et 100 observations offrent à nouveau le meilleur compromis.

5 Comparaison données brutes - données agrégées - données lissées


Afin de visualiser de manière synthétique l’ensemble du processus de traitement des données que nous avons mis en œuvre, nous proposons maintenant de superposer sur un même graphique les trois étapes de transformation : les données brutes (mesures toutes les 15 minutes), les données agrégées (moyennes par blocs de 1h15) et les données lissées par moyenne mobile (fenêtre de 50 observations). Cette visualisation comparative permettra d’apprécier l’effet cumulatif de chaque traitement et de confirmer que les tendances biologiques sont préservées tout au long du lissage.

Code
# Comparaison globale pour la chlorophylle
plot(
    data.date,
    data.Chl,
    label = "Données brutes (15 min)",
    xlabel = "Temps",
    ylabel = "Chlorophylle (µg/L)",
    title = "Comparaison des traitements - Chlorophylle",
    alpha = 0.3,
    color = "#2563eb",
    linewidth = 1
)

plot!(
    data_agregee.date_debut,
    data_agregee.Chl_moyenne,
    label = "Données agrégées (1h15)",
    alpha = 0.6,
    linewidth = 1.5,
    color = :darkred
)

plot!(
    data_agregee.date_debut,
    data_agregee.Chl_lissee_50,
    label = "Données lissées (fenêtre = 50)",
    linewidth = 2.5,
    color = :darkgreen
)
Code
# Comparaison globale pour les cyanobactéries
plot(
    data.date,
    data.Cyano,
    label = "Données brutes (15 min)",
    xlabel = "Temps",
    ylabel = "Cyanobactéries (µg/L)",
    title = "Comparaison des traitements - Cyanobactéries",
    alpha = 0.3,
    color = "#2563eb",
    linewidth = 1
)

plot!(
    data_agregee.date_debut,
    data_agregee.Cyano_moyenne,
    label = "Données agrégées (1h15)",
    alpha = 0.6,
    linewidth = 1.5,
    color = :darkred
)

plot!(
    data_agregee.date_debut,
    data_agregee.Cyano_lissee_50,
    label = "Données lissées (fenêtre = 50)",
    linewidth = 2.5,
    color = :darkgreen
)

Ces graphiques de comparaison illustrent de manière très claire l’efficacité du processus de traitement appliqué aux données.

Pour la chlorophylle, les données brutes (en bleu clair) présentent une très forte variabilité avec des pics extrêmement marqués pouvant atteindre plus de 600 µg/L. L’agrégation par blocs de 1h15 (en rouge) permet déjà une réduction importante de cette variabilité, ramenant les pics maximaux aux alentours de 150-200 µg/L. Enfin, le lissage par moyenne mobile avec une fenêtre de 50 observations (en vert foncé) produit une courbe parfaitement lisible qui semble conserver tous les événements biologiques majeurs tout en éliminant les fluctuations parasites.

Pour les cyanobactéries, le même processus de réduction progressive du bruit s’observe. Les données brutes montrent des pics pouvant atteindre 85 µg/L, particulièrement au printemps 2023. On observe notamment que les concentrations restent généralement faibles (< 5 µg/L) avec des épisodes de prolifération ponctuels et identifiables.

Ces visualisations confirment que notre stratégie de traitement des données est appropriée : elle permet d’éliminer efficacement le bruit de mesure tout en préservant intégralement l’information biologique pertinente pour l’étude des dynamiques dans le lac.

6 Conclusion


Ce travail avait pour objectif de traiter des données de surveillance environnementale issues de capteurs mesurant les concentrations en chlorophylle et en cyanobactéries dans un lac de région parisienne. Face à des données brutes présentant une forte variabilité et un bruit de mesure important à l’échelle de 15 minutes, nous avons mis en œuvre une stratégie de traitement en deux étapes.

Dans un premier temps, nous avons procédé à une agrégation des données par blocs de 1h15 (5 mesures consécutives), ce qui a permis de réduire significativement le volume de données et d’atténuer les fluctuations à très court terme. Cette première transformation a ramené les valeurs maximales de chlorophylle de plus de 600 µg/L à environ 150-200 µg/L, et celles des cyanobactéries de 85 µg/L à environ 20 µg/L.

Dans un second temps, nous avons appliqué un lissage par moyenne mobile sur les données agrégées. Après avoir testé plusieurs tailles de fenêtre (5, 10, 25, 50, 100 et 500 observations), nous avons identifié que les fenêtres de 50 et 100 observations offraient le meilleur compromis entre réduction du bruit et préservation des événements biologiques significatifs. Les fenêtres plus petites (5 et 10) conservaient trop de fluctuations parasites, tandis que la fenêtre de 500 observations produisait un lissage excessif effaçant certains pics importants.

Les visualisations finales superposant les données brutes, agrégées et lissées démontrent l’efficacité de notre approche. Les courbes finales révèlent clairement les dynamiques temporelles des concentrations en chlorophylle et en cyanobactéries, mettant en évidence des patterns saisonniers et des événements de prolifération (blooms) qui étaient difficiles à distinguer dans les données brutes. Notamment, nous observons des blooms majeurs de chlorophylle au printemps 2023, début 2024 et en été 2024, avec des profils similaires mais d’amplitude moindre pour les cyanobactéries.

Cette méthodologie de traitement des données constitue une étape essentielle avant toute analyse statistique ou modélisation ultérieure. Elle permet d’obtenir des séries temporelles exploitables, réduisant l’autocorrélation excessive des mesures à haute fréquence tout en conservant l’information biologique pertinente pour comprendre la dynamique écologique du lac étudié.